﻿//=============================================================================
// Библиотека 'WPFExcelReport' предназначена для связи и передачи данных c PLC
//=============================================================================

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace WPFExcelReport.S7
{
    /// <summary>
    /// Разбор данных контроллера S7
    /// </summary>
    internal class ParseItem
    {
        #region Константы

        /// <summary>
        /// Макс размер посылки, байт
        /// </summary>
        private const int PAKET_LENGTH = 460;

        #endregion

        #region Поля

        #region Общие структуры

        /// <summary>
        /// Список используемых DB для чтения
        /// </summary>
        private List<DB> _DBRead;

        /// <summary>
        /// Список используемых DB для записи
        /// </summary>
        private List<DB> _DBWrite;

        /// <summary>
        /// Меркерная обл для чтения
        /// </summary>
        private M _mRead;

        /// <summary>
        /// Меркерная обл для записи
        /// </summary>
        private M _mWrite;

        /// <summary>
        /// Значения переменных для чтения: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, ItemValue> _iReadRes;

        /// <summary>
        /// Значения переменных для записи: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, ItemValue> _iWriteRes;

        /// <summary>
        /// Адреса переменных для чтения: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, Addr> _iReadAddr;

        /// <summary>
        /// Адреса переменных для записи: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, Addr> _iWriteAddr;

        #endregion



        #endregion

        #region Свойства

        #endregion

        #region Конструктор объекта

        /// <summary>
        /// Конструктор объекта ParseItem
        /// </summary>
        internal ParseItem(List<DB> DBRead,
                           List<DB> DBWrite, 
                           M mRead,
                           M mWrite,
                           Dictionary<string, ItemValue> iReadRes,
                           Dictionary<string, ItemValue> iWriteRes,
                           Dictionary<string, Addr> iReadAddr,
                           Dictionary<string, Addr> iWriteAddr)
        {
            this._DBRead = DBRead;
            this._DBWrite = DBWrite;
            this._mRead = mRead;
            this._mWrite = mWrite;
            this._iReadRes = iReadRes;
            this._iWriteRes = iWriteRes;
            this._iReadAddr = iReadAddr;
            this._iWriteAddr = iWriteAddr;
        }

        #endregion
              
        #region Внутренние методы

       
        /// <summary>
        /// Получение объекта адреса переменной
        /// </summary>
        /// <param name="sAddr"> строка адреса</param>
        ///<returns> объект адреса</returns>
        private Addr GetAddr(string sAddr)
        {
            sAddr = sAddr.Trim().ToLower();
                      
            if (sAddr.Substring(0, 2) == "db")
            {
                ValType valType;
                int dbNum, startByte, length;

                string dbBytePatt = @"db(\d+)\.(dbb)(\d+)$";             // byte
                string dbIntPatt = @"db(\d+)\.(dbw)(\d+)$";              // int
                string dbDbdRealPatt = @"db(\d+)\.(dbd|real)(\d+)$";     // dint, real
                string dbStrPatt = @"db(\d+)\.(string)(\d+)\.(\d+)";     // string

                if (Regex.IsMatch(sAddr, dbIntPatt))
                {
                    dbNum = Convert.ToInt32(Regex.Replace(sAddr, dbIntPatt, "$1"));
                    valType = ValType.IntT;
                    startByte = Convert.ToInt32(Regex.Replace(sAddr, dbIntPatt, "$3"));
                    length = 2;
                }
                else if (Regex.IsMatch(sAddr, dbDbdRealPatt))
                {
                    dbNum = Convert.ToInt32(Regex.Replace(sAddr, dbDbdRealPatt, "$1"));
                    valType = (Regex.Replace(sAddr, dbDbdRealPatt, "$2") == "dbd") ? ValType.DIntT : ValType.RealT;
                    startByte = Convert.ToInt32(Regex.Replace(sAddr, dbDbdRealPatt, "$3"));
                    length = 4;
                }
                else if (Regex.IsMatch(sAddr, dbBytePatt))
                {
                    dbNum = Convert.ToInt32(Regex.Replace(sAddr, dbBytePatt, "$1"));
                    valType = ValType.ByteT;
                    startByte = Convert.ToInt32(Regex.Replace(sAddr, dbBytePatt, "$3"));
                    length = 1;
                }
                else  
                {
                    dbNum = Convert.ToInt32(Regex.Replace(sAddr, dbStrPatt, "$1"));
                    valType = ValType.StringT;
                    startByte = Convert.ToInt32(Regex.Replace(sAddr, dbStrPatt, "$3"));
                    length = Convert.ToInt32(Regex.Replace(sAddr, dbStrPatt, "$4"));
                }

                return new Addr(dbNum, valType, ValArea.DB, startByte, length);
            }
            else // меркер область
            {
                string mBytePatt = @"mb(\d+)$";             // byte
                string mIntPatt = @"mw(\d+)$";              // int
                string mDintPatt = @"md(\d+)$";             // dint
                string mRealPatt = @"mr(\d+)$";             // real
                string mStrPatt = @"mstr(\d+)\.(\d+)$";     // string
                ValType valType;
                int startByte, length;
                
                if (Regex.IsMatch(sAddr, mIntPatt))
                {
                    valType = ValType.IntT;
                    startByte = Convert.ToInt32(Regex.Replace(sAddr, mIntPatt, "$1"));
                    length = 2;
                }
                else if (Regex.IsMatch(sAddr, mDintPatt))
                {
                    valType = ValType.DIntT;
                    startByte = Convert.ToInt32(Regex.Replace(sAddr, mDintPatt, "$1"));
                    length = 4;
                }
                else if (Regex.IsMatch(sAddr, mRealPatt))
                {
                    valType = ValType.RealT;
                    startByte = Convert.ToInt32(Regex.Replace(sAddr, mRealPatt, "$1"));
                    length = 4;
                }
                else if (Regex.IsMatch(sAddr, mBytePatt))
                {
                    valType = ValType.ByteT;
                    startByte = Convert.ToInt32(Regex.Replace(sAddr, mBytePatt, "$1"));
                    length = 1;
                }
                else
                {
                    valType = ValType.StringT;
                    startByte = Convert.ToInt32(Regex.Replace(sAddr, mStrPatt, "$1"));
                    length = Convert.ToInt32(Regex.Replace(sAddr, mStrPatt, "$2"));
                }

                return new Addr(0, valType, ValArea.M, startByte, length);
            }
           

        }

        /// <summary>
        /// Обновление используемых байт DB
        /// </summary>
        /// <param name="db"> db</param>
        /// <param name="addr"> адрес переменной</param>
        /// <param name="operType"> тип операции</param>
        private void UpdateUseBytesDB(DB db, Addr addr, OperationType operType)
        {
            // Обновление списка используемых байт
            switch (operType)
            {
                case OperationType.Add:

                    while (db.Bytes.Count < (addr.StartByte + addr.Length)) db.Bytes.Add(0);

                    for (int i = addr.StartByte; i < (addr.StartByte + addr.Length); i++) db.Bytes[i]++;
                    break;

                case OperationType.Del:
                    for (int i = addr.StartByte; i < (addr.StartByte + addr.Length); i++) db.Bytes[i]--;
                    break;
            }

        }

        /// <summary>
        /// Обновление используемых байт M
        /// </summary>
        /// <param name="m"> меркер область</param>
        /// <param name="addr"> адрес переменной</param>
        /// <param name="operType"> тип операции</param>
        private void UpdateUseBytesM(M m, Addr addr, OperationType operType)
        {
            // Обновление списка используемых байт
            switch (operType)
            {
                case OperationType.Add:

                    while (m.Bytes.Count < (addr.StartByte + addr.Length)) m.Bytes.Add(0);

                    for (int i = addr.StartByte; i < (addr.StartByte + addr.Length); i++) m.Bytes[i]++;
                    break;

                case OperationType.Del:
                    for (int i = addr.StartByte; i < (addr.StartByte + addr.Length); i++) m.Bytes[i]--;
                    break;
            }

        }
                
        /// <summary>
        /// Корректировка границ чтения/записи DB
        /// </summary>
        /// <param name="db"> db</param>
        /// <param name="rw"> тип</param>
        private void CorrectBoundsDB(DB db, ValRW rw)
        {
            db.Bounds.Clear();

            int curNum = 0;              // тек номер байта
            bool isNewPaket = true;      // новый пакет

            switch (rw)
            {
                case ValRW.Read:

                    for (int i = 0; i < db.Bytes.Count; i++)
                    {
                        if (db.Bytes[i] > 0)
                            if (!isNewPaket) db.Bounds[db.Bounds.Count - 1] = i;
                            else
                            {
                                db.Bounds.Add(i);   // 1я граница, стоит
                                db.Bounds.Add(i);   // 2я граница, плавает
                                isNewPaket = false;
                            }

                        curNum++;

                        if (curNum > PAKET_LENGTH)
                        {
                            isNewPaket = true;
                            curNum = 0;
                        }                        
                    }
                    break;


                case ValRW.Write:                  // записывать можно ПОДРЯД макс PAKET_LENGTH байт

                    bool isPozFront = true;        // положит перепад - начало новой области байт

                    for (int i = 0; i < db.Bytes.Count; i++)
                    {
                        if (db.Bytes[i] > 0)
                            if (isNewPaket || isPozFront)
                            {
                                db.Bounds.Add(i);   // 1я граница, стоит
                                db.Bounds.Add(i);   // 2я граница, плавает
                                isNewPaket = false;
                                isPozFront = false;
                                curNum = 1;
                            }
                            else
                            {
                                db.Bounds[db.Bounds.Count - 1] = i;
                                curNum++;
                            }
                        else isPozFront = true;
                        

                        if (curNum > PAKET_LENGTH)
                        {
                            isNewPaket = true;
                            curNum = 0;
                        }
                    }                   
                    break;
            }
                        
        }

        /// <summary>
        /// Корректировка границ чтения/записи M
        /// </summary>
        /// <param name="m"> мерк обл</param>
        /// <param name="rw"> тип</param>
        private void CorrectBoundsM(M m, ValRW rw)
        {
            m.Bounds.Clear();

            int curNum = 0;              // тек номер байта
            bool isNewPaket = true;      // новый пакет

            switch (rw)
            {
                case ValRW.Read:

                    for (int i = 0; i < m.Bytes.Count; i++)
                    {
                        if (m.Bytes[i] > 0)
                            if (!isNewPaket) m.Bounds[m.Bounds.Count - 1] = i;
                            else
                            {
                                m.Bounds.Add(i);   // 1я граница, стоит
                                m.Bounds.Add(i);   // 2я граница, плавает
                                isNewPaket = false;
                            }

                        curNum++;

                        if (curNum > PAKET_LENGTH)
                        {
                            isNewPaket = true;
                            curNum = 0;
                        }
                    }
                    break;


                case ValRW.Write:                  // записывать можно ПОДРЯД макс PAKET_LENGTH байт

                    bool isPozFront = true;        // положит перепад - начало новой области байт

                    for (int i = 0; i < m.Bytes.Count; i++)
                    {
                        if (m.Bytes[i] > 0)
                            if (isNewPaket || isPozFront)
                            {
                                m.Bounds.Add(i);   // 1я граница, стоит
                                m.Bounds.Add(i);   // 2я граница, плавает
                                isNewPaket = false;
                                isPozFront = false;
                                curNum = 1;
                            }
                            else
                            {
                                m.Bounds[m.Bounds.Count - 1] = i;
                                curNum++;
                            }
                        else isPozFront = true;


                        if (curNum > PAKET_LENGTH)
                        {
                            isNewPaket = true;
                            curNum = 0;
                        }
                    }
                    break;
            }

        }
        #endregion

        #region Внешние методы
                
        /// <summary>
        /// Разбор добавляемой переменной для чтения
        /// </summary>
        /// <param name="item"> переменная для чтения</param>
        public void AddItemRead(ItemRead item)
        {
            // Получение адреса переменной
            Addr addr = GetAddr(item.Addr);

            // Переменная типа DB
            if (addr.ValArea == ValArea.DB)
            {
                // Проверка наличия DB, если нет - добавляем
                DB db = _DBRead.Find(e => e.Num == addr.DbNum);

                if (db == null)
                {
                    db = new DB(addr.DbNum);
                    _DBRead.Add(db);
                }

                // Добавляем адрес в DB
                db.Handls.Add(item.Addr);

                // Добавляем объект результата
                _iReadRes.Add(item.Addr, new ItemValue(item.Name, item.Addr, new object()));

                // Обновляем список адресов переменных
                _iReadAddr.Add(item.Addr, addr);

                // Обновляем список используемых байт DB
                UpdateUseBytesDB(db, addr, OperationType.Add);

                // Корректируем границы области чтения
                CorrectBoundsDB(db, ValRW.Read);
            }
            // Переменная типа M
            else
            {
                // Добавляем адрес 
                _mRead.Handls.Add(item.Addr);

                // Добавляем объект результата
                _iReadRes.Add(item.Addr, new ItemValue(item.Name, item.Addr, new object()));

                // Обновляем список адресов переменных
                _iReadAddr.Add(item.Addr, addr);

                // Обновляем список используемых байт
                UpdateUseBytesM(_mRead, addr, OperationType.Add);

                // Корректируем границы области чтения
                CorrectBoundsM(_mRead, ValRW.Read);

            }
        }

        /// <summary>
        /// Разбор удаляемой переменной для чтения
        /// </summary>
        /// <param name="item"> переменная для чтения</param>
        public void DelItemRead(HandlItemRead item)
        {
            // Берем DB
            Addr addr = _iReadAddr[item.Addr];

            // Переменная типа DB
            if (addr.ValArea == ValArea.DB)
            {
                DB db = _DBRead.Find(e => e.Num == addr.DbNum);

                // Удаляем адрес из DB
                db.Handls.Remove(item.Addr);

                // удаляем объект результата
                _iReadRes.Remove(item.Addr);

                // Обновляем список адресов переменных
                _iReadAddr.Remove(item.Addr);

                // Обновляем список используемых байт DB
                UpdateUseBytesDB(db, addr, OperationType.Del);

                // Корректируем границы области чтения
                CorrectBoundsDB(db, ValRW.Read);

                // удаляем db, если не используется
                if (db.Handls.Count == 0) _DBRead.Remove(db);
            }
            // Переменная типа M
            else
            {
                // Удаляем адрес
                _mRead.Handls.Remove(item.Addr);

                // удаляем объект результата
                _iReadRes.Remove(item.Addr);

                // Обновляем список адресов переменных
                _iReadAddr.Remove(item.Addr);

                // Обновляем список используемых байт DB
                UpdateUseBytesM(_mRead, addr, OperationType.Del);

                // Корректируем границы области чтения
                CorrectBoundsM(_mRead, ValRW.Read);
             }
        }

        /// <summary>
        /// Разбор добавляемой переменной для записи
        /// </summary>
        /// <param name="item"> переменная для записи</param>
        public void AddItemWrite(ItemWrite item)
        {
            // Получение адреса переменной
            Addr addr = GetAddr(item.Addr);

            // Переменная типа DB
            if (addr.ValArea == ValArea.DB)
            {

                // Проверка наличия DB, если нет - добавляем
                DB db = _DBWrite.Find(e => e.Num == addr.DbNum);

                if (db == null)
                {
                    db = new DB(addr.DbNum);
                    _DBWrite.Add(db);
                }

                // Добавляем объект результата
                _iWriteRes.Add(item.Addr, new ItemValue(item.Name, item.Addr, new object()));

                // Обновляем список адресов переменных
                _iWriteAddr.Add(item.Addr, addr);
            }
            // Переменная типа M
            else
            {
                // Добавляем объект результата
                _iWriteRes.Add(item.Addr, new ItemValue(item.Name, item.Addr, new object()));

                // Обновляем список адресов переменных
                _iWriteAddr.Add(item.Addr, addr);
            }

        }

        /// <summary>
        /// Разбор удаляемой переменной для записи
        /// </summary>
        /// <param name="item"> переменная для записи</param>
        public void DelItemWrite(HandlItemWrite item)
        {
            // Берем DB
            Addr addr = _iWriteAddr[item.Addr];

            // Переменная типа DB
            if (addr.ValArea == ValArea.DB)
            {
                DB db = _DBWrite.Find(e => e.Num == addr.DbNum);

                // Удаляем объект результата
                _iWriteRes.Remove(item.Addr);

                // Обновляем список адресов переменных
                _iWriteAddr.Remove(item.Addr);

                // удаляем db, если не используется
                if (_iWriteAddr.Count == 0) _DBWrite.Remove(db);
            }
            // Переменная типа M
            else
            {
                // Удаляем объект результата
                _iWriteRes.Remove(item.Addr);

                // Обновляем список адресов переменных
                _iWriteAddr.Remove(item.Addr);
            }
        }

        /// <summary>
        /// Подготовка переменной для записи
        /// </summary>
        /// <param name="item"> переменная для записи</param>
        public void UpdItemWrite(HandlItemWrite item)
        {
            // Берем DB
            Addr addr = _iWriteAddr[item.Addr];

            // Переменная типа DB
            if (addr.ValArea == ValArea.DB)
            {
                DB db = _DBWrite.Find(e => e.Num == addr.DbNum);

                // заполняем значение
                _iWriteRes[item.Addr].Value = item.Value;

                // Добавляем адрес в DB
                if (!db.Handls.Contains(item.Addr)) db.Handls.Add(item.Addr);

                // Обновляем список используемых байт DB
                UpdateUseBytesDB(db, addr, OperationType.Add);

                // Корректируем границы области записи
                CorrectBoundsDB(db, ValRW.Write);
            }
            // Переменная типа M
            else
            {
                // заполняем значение
                _iWriteRes[item.Addr].Value = item.Value;

                // Добавляем адрес в DB
                if (!_mWrite.Handls.Contains(item.Addr)) _mWrite.Handls.Add(item.Addr);

                // Обновляем список используемых байт DB
                UpdateUseBytesM(_mWrite, addr, OperationType.Add);

                // Корректируем границы области записи
                CorrectBoundsM(_mWrite, ValRW.Write);
            }
        }

       
        #endregion
              
    }
}
